package cn.mickey.exchange.aop;

import cn.hutool.core.date.SystemClock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;


/**
 * @ClassName RedisAOP
 * @description: redis 切面缓存
 **/
@Aspect
@Service
@Slf4j
public class RedisAOP {
    /**
     * redis 缓存存活时长（分钟）
     */
    private static final Integer TIME_OUT = 30;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * @Title: queryCachePointcut
     * @Description: 定义切点为缓存注解
     **/
    @Pointcut("@within(cn.mickey.exchange.annotation.RedisCache)")
    public void queryCachePointcut() {
    }

    @Around("queryCachePointcut()")
    public Object Interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = SystemClock.now();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //类路径名
        String classPathName = joinPoint.getTarget().getClass().getName();
        //类名
        String className = classPathName.substring(classPathName.lastIndexOf(".") + 1);
        //获取方法名
        String methodName = signature.getMethod().getName();
        //参数名
        String[] strings = signature.getParameterNames();
        //参数值
        Object[] args = joinPoint.getArgs();
        String key = className + "_" + methodName + "_" + Arrays.toString(strings) + ":" + Arrays.toString(args);
        if (isSetOrGet(methodName)) {
            Object data = getObject(beginTime, joinPoint, key);
            if (data != null) {
                return data;
            }
            return joinPoint.proceed();
        } else if (isDelCache(methodName)) {
            Set<String> keys = redisTemplate.keys(className + "*");
            //log.info(keys.toString());
            redisTemplate.delete(keys);
            //log.info("执行方法 : [ " + methodName + " ] : 清除 key 包含 [ " + className + " ] 的缓存数据");
            log.info("执行方法 : [{}] : 清除 key 包含 [{}] 的缓存数据", methodName, className);
            log.info("AOP 缓存切面处理 >>>> end 耗时：{} 毫秒", SystemClock.now() - beginTime);
            return joinPoint.proceed();
        }
        // 调用原始方法
        return joinPoint.proceed();
    }

    /**
     * 执行查询方法时设置或获取缓存
     *
     * @param methodName
     * @return boolean
     */
    public boolean isSetOrGet(String methodName) {
        return (methodName.contains("find") && methodName.substring(0, 4).equalsIgnoreCase("find"))
                || (methodName.contains("get") && methodName.substring(0, 3).equalsIgnoreCase("get"))
                || (methodName.contains("check") && methodName.substring(0, 5).equalsIgnoreCase("check"));
    }

    /**
     * 执行修改方法时清理缓存
     *
     * @param methodName
     * @return boolean
     */
    public boolean isDelCache(String methodName) {
        return (methodName.contains("add") && methodName.substring(0, 3).equalsIgnoreCase("add"))
                || (methodName.contains("insert") && methodName.substring(0, 6).equalsIgnoreCase("insert"))
                || (methodName.contains("update") && methodName.substring(0, 6).equalsIgnoreCase("update"))
                || (methodName.contains("delete") && methodName.substring(0, 6).equalsIgnoreCase("delete"))
                || (methodName.contains("send") && methodName.substring(0, 4).equalsIgnoreCase("send"));
    }

    /**
     * @param beginTime : 切面开始时间
     * @param joinPoint : 切面对象
     * @param key       : 获取redis数据的key值
     * @return java.lang.Object
     * @Title: getObject
     * @Description: 使用key获取数据 不存在则查询添加
     **/
    private Object getObject(long beginTime, ProceedingJoinPoint joinPoint, String key) throws Throwable {
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        Boolean hasKey = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(hasKey)) {
            //缓存中获取到数据，直接返回。
            Object object = operations.get(key);
            //log.info("从缓存中获取到 key 为 [" + key + " ] : 的数据 >>>> " + object);
            log.info("从缓存中获取到 key 为 [{}] : 的数据 >>>> {}", key, object);
            log.info("AOP 缓存切面处理 >>>> end 耗时：{} 毫秒", SystemClock.now() - beginTime);
            return object;
        }
        // 缓存中没有数据，调用原始方法查询数据库
        Object object = joinPoint.proceed();
        // 设置超时时间30分钟
        operations.set(key, object, TIME_OUT, TimeUnit.MINUTES);
        //log.info("向 Redis 添加 key 为 [" + key + " ] , 存活时长为 " + TIME_OUT + " min 的数据 >>>> " + object);
        log.info("向 Redis 添加 key 为: [{}] , 存活时长为: {} min 的数据 >>>> {}", key, TIME_OUT, object);
        log.info("AOP 缓存切面处理 >>>> end 耗时：{} 毫秒", SystemClock.now() - beginTime);
        return object;
    }

    /*@Autowired(required = false)
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer(); //序列化为String
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); //序列化为Json
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        this.redisTemplate = redisTemplate;
    }*/
}